package top.v5it.japi.plus.spring.boot.autoconfigure;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.spring.EnableSpringUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.util.Streamable;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.v5it.japi.plus.common.tuple.Tuple4;
import top.v5it.japi.plus.common.tuple.Tuples;
import top.v5it.japi.plus.core.exception.handler.GlobalJapiResponseExceptionHandler;
import top.v5it.japi.plus.core.spring.web.JapiDispatcherServlet;
import top.v5it.japi.plus.core.spring.web.interceptor.JapiInterceptorConfigurationSelector;
import top.v5it.japi.plus.core.spring.web.interceptor.JapiTokenInterceptor;
import top.v5it.japi.plus.core.spring.web.interceptor.TraceInterceptor;
import top.v5it.japi.plus.log.OperLogConfigurationSelector;
import top.v5it.japi.plus.spring.boot.autoconfigure.properties.JapiProperties;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;


/**
 * 自动装配japi
 *
 * @author zhanpu
 * @date 2020-11-20
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({WebMvcConfigurer.class, JapiDispatcherServlet.class})
@EnableConfigurationProperties({JapiProperties.class})
@Import({
        SupportComponentConfiguration.class,
        SupportComponentConfiguration.IdCenterConfiguration.class,
        SupportComponentConfiguration.LimitConfiguration.class,
        SupportComponentConfiguration.JdbcAutoConfiguration.class,
        SupportComponentConfiguration.JpaAutoConfiguration.class,
        SupportComponentConfiguration.JapiLoadRedisSetConfiguration.class,
        OperLogConfigurationSelector.class,
        JapiInterceptorConfiguration.class,
        JapiInterceptorConfiguration.HmacAuthorized.class,
        JapiInterceptorConfiguration.ShaAuthorized.class,
        JapiXssFilterConfiguration.class,
        ValidatorConfiguration.class,
        GlobalJapiResponseExceptionHandler.class,
        JapiInterceptorConfigurationSelector.class
})
@EnableSpringUtil
@Slf4j
@RequiredArgsConstructor
public class JapiAutoConfigure {

    /**
     * 分发器注入
     *
     * @return {@link DispatcherServlet}
     */
    @Bean
    @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        log.info(">>>>>>>>>>[Init DispatcherServlet]<<<<<<<<<<");
        return new JapiDispatcherServlet();
    }

    @Bean("traceInterceptor")
    public TraceInterceptor traceInterceptor() {
        log.info(">>>>>>>>>>[Init TraceInterceptor]<<<<<<<<<<");
        return new TraceInterceptor();
    }

    /**
     * MVC拦截器注册
     *
     * @param handlerInterceptors 注入的拦截器
     * @param japiProperties      配置文件信息
     * @return {@link WebMvcConfigurer}
     */
    @Bean
    public WebMvcConfigurer interceptorWebMvcConfigurer(Map<Integer, HandlerInterceptor> handlerInterceptors, JapiProperties japiProperties) {
        log.info(">>>>>>>>>>[Init WebMvcConfigurer]<<<<<<<<<<");
        return new WebMvcConfigurer() {

            @Autowired
            @Qualifier("traceInterceptor")
            private TraceInterceptor traceInterceptor;

            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(traceInterceptor);
                final JapiProperties.Interceptor interceptor = japiProperties.getInterceptor();
                final String[] includePathPatterns = interceptor.getIncludes();
                final String[] excludePathPatterns = interceptor.getExcludes();
                final String[] notokenPathPatterns = interceptor.getToken().getExcludes();
                final String[] excludes = ArrayUtil.addAll(notokenPathPatterns, excludePathPatterns);
                final JapiProperties.Interceptor.Properties[] properties = interceptor.getProperties();

                final Map<String, HandlerInterceptor> japiInterceptorMap = SpringUtil.getBeansOfType(HandlerInterceptor.class);

                // 当前程序新增拦截器
                List<Tuple4<HandlerInterceptor, Integer, String[], String[]>> addInterceptors =
                        Streamable.of(properties)
                                .filter(v -> MapUtil.isNotEmpty(japiInterceptorMap) && japiInterceptorMap.containsKey(v.getClazz()))
                                .map(v -> Tuples.of(japiInterceptorMap.get(v.getClazz()), v.getOrder(), ArrayUtil.isEmpty(v.getIncludes()) ? includePathPatterns : v.getIncludes(), ArrayUtil.isEmpty(v.getExcludes()) ? excludePathPatterns : ArrayUtil.addAll(v.getExcludes(), excludePathPatterns)))
                                .toList();

                // 当前应用程序中已经存在的拦截器
                List<Tuple4<HandlerInterceptor, Integer, String[], String[]>> japiInterceptors =
                        Streamable.of(handlerInterceptors.entrySet())
                                .filter(v -> Objects.nonNull(v.getValue()))
                                .map(v -> Tuples.of(v.getValue(), v.getKey(), includePathPatterns, v.getValue() instanceof JapiTokenInterceptor ? excludes : excludePathPatterns))
                                .toList();

                // 合并拦截器
                ArrayList<Tuple4<HandlerInterceptor, Integer, String[], String[]>> tuple4s = new ArrayList<>(japiInterceptors);
                tuple4s.addAll(addInterceptors);

                // 注册拦截器
                tuple4s.forEach(v ->
                        registry.addInterceptor(v.getOne())
                                .addPathPatterns(v.getThree())
                                .excludePathPatterns(v.getFour())
                                .order(v.getTwo())
                );
            }
        };
    }
}
